﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Fluid;
using Fluid.Values;
using Nito.AsyncEx;

namespace ZYing.LiquidThemeEngine.Paginating
{
    public class PaginationValue : FluidValue
    {
        internal class ValueCollection : List<FluidValue>
        {
            public int PageSize { get; set; }
            public int Total { get; set; }
        }
        public PaginationValue(IPaginationSource source, TemplateOptions options)
        {
            Source = source ?? throw new ArgumentNullException(nameof(source));
            TemplateOptions = options ?? throw new ArgumentNullException(nameof(options));
        }

        public IPaginationSource Source { get; }

        public TemplateOptions TemplateOptions { get; }
        public int PageSize { get; private set; } = Constants.DefaultPageSize;
        public int Total => _items != null ? _items.Total : 0;
        public override FluidValues Type => FluidValues.Array;
        private ValueCollection _items = null;
        private async Task PreloadAsync()
        {
            var result = await Source.PaginateAsync(this.PageSize);
            var collection = new ValueCollection { Total = result.Total, PageSize = this.PageSize };
            foreach (var item in result.Items)
            {
                collection.Add(Create(item, this.TemplateOptions));
            }
            _items = collection;
        }
        internal async Task PaginateAsync(Int32 pageSize)
        {
            if (pageSize > Constants.MaxPageSize)
            {
                pageSize = Constants.MaxPageSize;
            }
            else if (pageSize < Constants.MinPageSize)
            {
                pageSize = Constants.DefaultPageSize;
            }
            PageSize = pageSize;
            if (_items == null || _items.PageSize != this.PageSize) await PreloadAsync();
        }
        private ValueCollection GetItems()
        {
            if (_items == null || _items.PageSize != this.PageSize) AsyncContext.Run(this.PreloadAsync);
            return _items;
        }


        public override bool Equals(FluidValue other)
        {
            if (other == null || other.IsNil()) return false;
            return other is PaginationValue value && value.Source == Source;
        }

        protected override FluidValue GetValue(string name, TemplateContext context)
        {
            switch (name)
            {
                case "total":
                    return NumberValue.Create(GetItems().Total);
                case "size":
                    return NumberValue.Create(GetItems().Count);
                case "first":
                    {
                        var items = GetItems();
                        if (items.Count > 0) return items[0];
                    }
                    break;
                case "last":
                    {
                        var items = GetItems();
                        if (items.Count > 0) return items[items.Count - 1];
                    }
                    break;
            }

            return NilValue.Instance;
        }

        public override bool ToBooleanValue()
        {
            return true;
        }

        public override decimal ToNumberValue()
        {
            return GetItems().Total;
        }

        public override bool Contains(FluidValue value)
        {
            return GetItems().Contains(value);
        }

        public override IEnumerable<FluidValue> Enumerate()
        {
            return GetItems();
        }

        public override object ToObjectValue()
        {
            return Source;
        }

        public override string ToStringValue()
        {
            return string.Join(String.Empty, GetItems().Select(x => x.ToStringValue()));
        }

        public override int GetHashCode()
        {
            return Source.GetHashCode();
        }

        public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
        {
            AssertWriteToParameters(writer, encoder, cultureInfo);

            foreach (var v in GetItems()) writer.Write(v.ToStringValue());
        }
    }
}